#include "include/NIOServer.h"

#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <iostream>
#include <map>
#include <memory>

// 127.0.0.1:8000
using namespace std;

/*向sock_fd发送所有数据，AllLen数据长度，buf数据数组*/
int NIOServer::sendAllData(int sock_fd, int AllLen, char *buf)
{
    int offset = 0, n = 0;
    // send数据  一定要发完
    while (offset < AllLen) {
        int Block = min(AllLen - offset, 1450); // 一次发多少
        n = write(sock_fd, buf + offset, Block);

        if (n == 0) { // 对方关闭了
            offset = 0;
            break;
        }
        if (n == -1) {                // 错误码
            if (errno == EWOULDBLOCK) // 缓冲区满了，先等一等
            {
                cout << "write:EWOULDBLOCK " << EWOULDBLOCK << endl;
                usleep(10000);
                continue;
            } else if (errno == EINTR) { // 被中断了 数据无效
                cout << "write:EINTR " << EINTR << endl;
                continue;
            } else {
                cout << "write:未知错误！" << endl;
                offset = 0;
                break;
            }
        }
        // cout << "write:errno " << errno<< endl;
        offset += n; // 已经发送的数据
    }
    if (!offset) // 什么都没有发送成功
    {
        close(sock_fd);
        return -1;
    }
    return 0;
}

// 线程处理函数，必须符合 HTaskFunc 类型
void do_task_for_send_func(unique_ptr<NIOTaskParam> &param)
{
    int cfd = *(param->cfd);
    char *buf2 = param->buf2.get();
    unique_ptr<HqHttpResHead> res = move(param->res);

    int len;
    while ((len = res->mfs->getnext(100000))) {
        // send数据
        int ret = NIOServer::sendAllData(cfd, len, res->mfs->buf); // head
        if (ret == -1) {
            printf("sendAllData==-1, close fd = %d \n", cfd);
            close(cfd);
            break;
        }
        printf(">> %d, %d,%d,%d \n", cfd, len, res->mfs->offset,
               res->mfs->size);
    }
    // 自动 ~HqHttpResHead
}

NIOServer::NIOServer(string h, int p, int thread_pool_num)
{
    host = h;
    port = p;
    _buf = new char[MAXBUF];
    sendPool.reset(new Thread_pool<NIOTask, NIOTaskFun, NIOTaskParam>(
            thread_pool_num));
}
NIOServer::~NIOServer()
{
    printf("~NIOServer() \n");
    if (_buf)
        delete[] _buf;
}

int NIOServer::startNIOServer()
{
    /*重要！设置SIGPIPE信号的行为为空，否则对端关闭之后再写数据，系统默认会关闭“进程”，直接闪退*/
    set_SIGPIPEN_NULL();

    epfd = epoll_create(256);                   // 生成epoll句柄
    listenfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
    // 设置复用
    int on = 1;
    setsockopt(
            listenfd, SOL_SOCKET, SO_REUSEADDR, &on,
            sizeof(on)); // 允许already use

    ev.data.fd = listenfd; // 设置与要处理事件相关的文件描写叙述符
    ev.events = EPOLLIN; // 设置要处理的事件类型

    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); // 注冊epoll事件

    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(this->host.c_str());
    serveraddr.sin_port = htons(this->port);
    bind(listenfd, (struct sockaddr *)&serveraddr,
         sizeof(serveraddr)); // 绑定套接口

    int ret = listen(listenfd, LISTENQ); // 转为监听套接字
    if (ret != 0) {
        cout << "listen error!" << endl;
    } else {
        cout << "listening ... :: (" << this->host << ":" << this->port << ")"
             << endl;
    }

    while (1) {
        nfds = epoll_wait(epfd, events, MAX_EVENT, TIME_OUT); // 等待事件发生
        // 处理所发生的全部事件
        for (i = 0; i < nfds; ++i) {
            if (events[i].data.fd == listenfd) // 有新的连接
            {
                handle_accept();
            }
            // 出错
            else if (events[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {
                handle_error();
            } else if (events[i].events & EPOLLIN) // 可读 可写 事件
            {
                handle_in();
            }
        }
    }
    return 0;
}

int NIOServer::handle_accept()
{
    static socklen_t clilen = sizeof(struct sockaddr_in);
    conn_fd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
    /* 修改connfd为非阻塞读 */
    setNonBlock(conn_fd);

    printf("收到一个请求 : %s, %d\n", inet_ntoa(clientaddr.sin_addr),
           clientaddr.sin_port);
    ev.data.fd = conn_fd;
    ev.events =
            EPOLLIN | EPOLLOUT | EPOLLET; // 设置监听事件  EPOLLIN | EPOLLET
    epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev); // 新增套接字
    return 0;
}
int NIOServer::handle_error()
{
    cout << "EPOLLERR EPOLLHUP EPOLLRDHUP " << events[i].events << endl;
    close(events[i].data.fd);
    return 0;
}
// 0 正常 -2 关闭
int NIOServer::handle_in()
{
    /*
    在ET模式下，(1)、如果用阻塞IO+while循环，当最后一个数据读取完后，程序是无法立刻跳出while循环的，
    因为阻塞IO会在 while(true){ int len=recv();   }这里阻塞住，
    除非对方关闭连接或者recv出错，这样程序就无法继续往下执行，
    这一次的epoll_wait没有办法处理其它的连接，会造成延迟、并发度下降。
    (2)、如果是非阻塞IO+while循环，当读取完数据后，recv会立即返回-1，
    并将errno设置为EAGAIN或EWOULDBLOCK,这就表示数据已经读取完成，已经没有数据了，
    可以退出循环了。这样就不会像阻塞IO一样卡在那里，这就减少了不必要的等待时间，性能自然更高。
    */
    if ((_sock_fd = events[i].data.fd) < 0)
        return 0;
    // 读取
    _offset = 0;
    while (1) {
        n = recv(_sock_fd, _buf + _offset, 1450, 0);
        if (n == 0) { // 对方关闭了
            _offset = 0;
            break;
        }
        if (n == -1) {
            // 判断 错误码
            // EWOULDBLOCK表示发送时套接字发送缓冲区已满，或接收时套接字接收缓冲区为空
            if (errno == EWOULDBLOCK) {
                cout << "recv:EWOULDBLOCK, now offset="<< _offset << endl;
                break;
            } else if (errno == EINTR) {// 被中断
                cout << "recv:continue, now offset="<< _offset << endl;
                continue;
            }else {
                cout << "1未知错误！\n";
                return -2;
            }
        }
        _offset += n;
    }
    if (_offset == 0) {
        close(_sock_fd);
        return 0;
    }

    // your virtual function hear
    int RET = unpack_and_pack(_buf, _offset, _sock_fd);
    // end your code
    return RET;
}

// 对 buf 和 offset 进行解包，然后进行相应的操作
// return： 0正常，-1关闭sock继续，-2错误！
int NIOServer::unpack_and_pack(char *&buf, int offset, int fd)
{
    // 解包，一次性读完的
    printf("NIOServer 接收到：\n");
    for (int i = 0; i < offset; ++i)
        putchar(buf[i]);
    printf("\nEND\n");

    // 处理函数
    cout << "正在处理:\n" << endl;

    //
    const char *body = "your NIO server is started!\n"
                       "This is the most basic server, and you can inherit"
                       " it to achieve more functions";
    sprintf(buf,
            "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-"
            "Length: %d\r\n\r\n%s",
            strlen(body), body);
    NIOServer::sendAllData(fd, strlen(buf), buf);

    printf("该请求处理完毕:connfd=%d\n", fd);
    // close(sock_fd); //如果是connnction=close
    return 0; // 正常
}
